package com.zws.cucumber.plugin.procedure;

import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
import com.zws.cucumber.ZwsUtils;
import com.zws.cucumber.plugin.ZwsPlugin;
import com.zws.cucumber.util.colorfullog.ZwsColorfulLogger;
import io.cucumber.plugin.event.*;
import io.cucumber.core.eventbus.AbstractEventPublisher;
import io.cucumber.core.eventbus.EventBus;
import io.cucumber.core.runner.CoreRunnerTestCaseHelper;
import io.cucumber.core.runtime.SynchronizedEventBus;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.zws.cucumber.plugin.procedure.ZwsProcedureUtil.argumentsMap;
import static com.zws.cucumber.plugin.procedure.ZwsProcedureUtil.procedureMap;

public class ZwsProcedurePluginImpl implements ZwsPlugin {

    private static final Logger logger = ZwsColorfulLogger.of(ZwsProcedurePluginImpl.class);

    public static EventBus eventBus;

    static {
        ch.qos.logback.classic.Logger logbackLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("root");
        ConsoleAppender<ILoggingEvent> appender = (ConsoleAppender<ILoggingEvent>) logbackLogger.getAppender("STDOUT");
        LayoutWrappingEncoder<ILoggingEvent> encoder = (LayoutWrappingEncoder<ILoggingEvent>) appender.getEncoder();
        appender.setEncoder(new ZwsProcedurePatternLayoutEncoder(appender, (PatternLayoutEncoder) encoder));
    }

    public static void handleTestCaseStarted(TestCaseStarted event) {
        try {
            handleTestCaseStarted0(event);
        } catch (Throwable e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    private static void handleTestCaseStarted0(TestCaseStarted event) throws Throwable {
        TestCase testCase = event.getTestCase();
        logger.info("test case \"{}\" with tags {}", testCase.getName(), testCase.getTags());
        if (!testCase.getTags().contains("@Procedure")) {
            return;
        }
        logger.info("treat test case \"{}\" as procedure", testCase.getName());

        Pair<String, List<String>> signature = ZwsProcedureUtil.parseProcedureSignature(testCase.getName());
        String procedureName = signature.getKey();
        List<String> procedureArgumentList = signature.getValue();
        if (procedureMap.containsKey(procedureName)) {
            throw new IllegalArgumentException("Procedure '" + procedureName + "' already existed");
        }
        // clone and save it
        procedureMap.put(procedureName, CoreRunnerTestCaseHelper.cloneTestCaseAsDefinition(testCase));
        argumentsMap.put(procedureName, procedureArgumentList);

        System.out.println(ZwsProcedureUtil.procedureMap.keySet());
        System.out.println(ZwsProcedureUtil.argumentsMap);
        // make it dry run
        CoreRunnerTestCaseHelper.setDryRun(testCase);
    }

    public static void handleTestCaseFinished(Object event) {
        TestStepFinished e = (TestStepFinished) event;
        if (e.getTestCase().getTags().contains("@Procedure")) {
            Result origin = e.getResult();
            Result fixed;
            if (origin.getStatus() == Status.SKIPPED) {
                fixed = new Result(Status.PASSED, origin.getDuration(), origin.getError());
            } else {
                fixed = origin;
            }

            changeResult(e, fixed);
        }
    }

    static void changeResult(TestStepFinished event, Result result) {
        ZwsUtils.setFieldValue(TestStepFinished.class, "result", event, result);
    }

    @Override
    public String getName() {
        return "Procedure";
    }

    @Override
    public int getPriority() {
        return 10;
    }

    @Override
    public void init0(EventPublisher publisher) {
        eventBus = (EventBus) publisher;
        publisher.registerHandlerFor(TestCaseStarted.class, ZwsProcedurePluginImpl::handleTestCaseStarted);
    }

    @Override
    public void init1(EventPublisher publisher) {
        EventBus delegate = ZwsUtils.getFieldValue(SynchronizedEventBus.class, "delegate", publisher);
        Map<Class<? extends Event>, List<EventHandler>> handlers = ZwsUtils.getFieldValue(AbstractEventPublisher.class, "handlers", delegate);
        // first step to change the status to passed if it is skipped
        List<EventHandler> handlerList = handlers.computeIfAbsent(TestStepFinished.class, k -> new ArrayList<>());
        handlerList.add(0, ZwsProcedurePluginImpl::handleTestCaseFinished);
    }
}
